package daobeauty.interceptor.invoke.factory;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;

import daobeauty.interceptor.DaoMetaData;
import daobeauty.interceptor.invoke.factory.impl.CreateCriteriaInvokeFactory;
import daobeauty.interceptor.invoke.factory.impl.CrudDeleteInvokeFactory;
import daobeauty.interceptor.invoke.factory.impl.CrudSaveInvokeFactory;
import daobeauty.interceptor.invoke.factory.impl.CrudSaveOrUpdateInvokeFactory;
import daobeauty.interceptor.invoke.factory.impl.CrudUpdateInvokeFactory;
import daobeauty.interceptor.invoke.factory.impl.FindAllInvokeFactory;
import daobeauty.interceptor.invoke.factory.impl.FindByExampleInvokeFactory;
import daobeauty.interceptor.invoke.factory.impl.FindByNativeQueryInvokeFactory;
import daobeauty.interceptor.invoke.factory.impl.FindByParsingInvokeFactory;
import daobeauty.interceptor.invoke.factory.impl.FindByPrimaryKeyInvokeFactory;
import daobeauty.interceptor.invoke.factory.impl.FindCountByParsingInvokeFactory;
import daobeauty.interceptor.invoke.factory.impl.FindExistsByParsingInvokeFactory;
import daobeauty.interceptor.invoke.factory.impl.GetDomainInvokeFactory;
import daobeauty.interceptor.invoke.factory.impl.GetNamedQueryInvokeFactory;
import daobeauty.interceptor.invoke.factory.impl.GetPrimaryKeyInvokeFactory;
import daobeauty.interceptor.invoke.factory.impl.NamedQueryDelegateInvokeFactory;

public final class InvokeStrategyFactoryManager {
	private static final InvokeStrategyFactoryManager INSTANCE = new InvokeStrategyFactoryManager();
	
	public static InvokeStrategyFactoryManager getInstance(){
		return INSTANCE;
	}
	
	private HashMap<String, InvokeStrategyFactory> invokeStrategyFactoriesByMethodNamePrefix = new HashMap<String, InvokeStrategyFactory>();
	private ArrayList<String> methodNamePrefixes = new ArrayList<String>();
	private Comparator<String> comparator = Collections.reverseOrder(new StringLengthComparator());
	private ArrayList<FilteringInvokeStrategyFactory> filteringInvokeStrategyFactories = new ArrayList<FilteringInvokeStrategyFactory>();
	
	private InvokeStrategyFactoryManager(){
		register(new CrudSaveInvokeFactory(), "save");
		register(new CrudSaveOrUpdateInvokeFactory(), "saveOrUpdate");
		register(new CrudDeleteInvokeFactory(), "delete");
		register(new CrudUpdateInvokeFactory(), "update");
		register(new GetDomainInvokeFactory(), "getDomain");
		register(new GetPrimaryKeyInvokeFactory(), "getPrimaryKey");
		register(new GetNamedQueryInvokeFactory(), "getNamedQuery");
		register(new CreateCriteriaInvokeFactory(), "createCriteria");
		register(new NamedQueryDelegateInvokeFactory(), (String) null);
		register(new FindByPrimaryKeyInvokeFactory(), new String[]{"findByPrimaryKey", "findById"});
		register(new FindByExampleInvokeFactory(), "findByExample");
		register(new FindAllInvokeFactory(), "findAll");
		register(new FindByNativeQueryInvokeFactory(), "findByNativeQuery");
		register(new FindByParsingInvokeFactory(), "findBy");
		register(new FindCountByParsingInvokeFactory(), new String[]{"findCountBy", "findCountDistinctBy"});
		register(new FindExistsByParsingInvokeFactory(), "findExistsBy");
	}
	
	public void register(InvokeStrategyFactory factory, String[] methodNamePrefixes){
		for (String methodNamePrefix:methodNamePrefixes){
			register(factory, methodNamePrefix);
		}
	}
	
	public void register(InvokeStrategyFactory factory, String methodNamePrefix){
		if (factory instanceof FilteringInvokeStrategyFactory){
			filteringInvokeStrategyFactories.add((FilteringInvokeStrategyFactory)factory);
		}
		else{
			final String lowerMethodName = methodNamePrefix.toLowerCase();
			invokeStrategyFactoriesByMethodNamePrefix.put(lowerMethodName, factory);
			methodNamePrefixes.add(lowerMethodName);
			Collections.sort(methodNamePrefixes, comparator);
		}
	}
	
	public FilteringInvokeStrategyFactory getFactory(DaoMetaData daoMetaData, Method daoMethod){
		for (FilteringInvokeStrategyFactory factory:filteringInvokeStrategyFactories){
			if (factory.accept(daoMetaData, daoMethod)){
				return factory;
			}
		}
		return null;
	}
	
	// TODO: fuer spaeter eigeneklasse: method constraints (z.b. returntype, name etc) 
	public InvokeStrategyFactory getFactory(Method daoMethod){
		final String lowerMethodName = daoMethod.getName().toLowerCase();
		for (String methodNamePrefix:methodNamePrefixes){
			if (lowerMethodName.startsWith(methodNamePrefix)){
				return invokeStrategyFactoriesByMethodNamePrefix.get(methodNamePrefix);
			}
		}
		return null;
	}
}
